package edu.pku.sei.modeler.handler;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.eclipse.draw2d.Border;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Cursors;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Locator;
import org.eclipse.draw2d.MarginBorder;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.draw2d.text.FlowPage;
import org.eclipse.draw2d.text.TextFlow;
import org.eclipse.gef.DragTracker;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.handles.AbstractHandle;
import org.eclipse.gef.palette.ConnectionCreationToolEntry;
import org.eclipse.gef.palette.CreationToolEntry;
import org.eclipse.gef.palette.PaletteEntry;
import org.eclipse.gef.requests.CreationFactory;
import org.eclipse.gef.tools.ConnectionDragCreationTool;
import org.eclipse.gef.tools.CreationTool;
import org.eclipse.gef.ui.palette.PaletteMessages;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;



/**
 * An ActionHandle configured by a ToolEntry.
 * The ToolEntry provides graphical informations (icons, label, ..) and a factory
 * provided as is to the request.
 * An handle is responsible of:
 * <ul>
 * <li>drawing the figure</li>
 * <li>returning a DragTracker when the button is pressed over the handle</li>
 * <li></li>
 * </ul>
 */
public class CreationToolEntryActionHandle extends AbstractHandle {

	/**
	 * tool describing the handle.
	 */
	protected CreationToolEntry toolEntry;

	/**
	 * The small image to display. Lazy creation
	 */
	private Image smallImage;

	/**
	 * 
	 */
	private static final Color lightBlue = ColorConstants.lightBlue;

	/**
	 * 
	 */
	private static final Border TOOLTIP_BORDER = new MarginBorder(0, 2, 1, 0);

	/**
	 * 
	 */
	private static ImageCache globalImageCache;

	/**
	 * Create a ActionHandle.
	 * 
	 * @param locator The locator used to locate the handle on the editPart.
	 * @param owner 
	 * @param tool The tool used to configure the handle.
	 * @param toolEntry 
	 */
	public CreationToolEntryActionHandle(GraphicalEditPart owner,
			CreationToolEntry toolEntry, Locator locator) {
		super(owner, locator, Cursors.CROSS);
		this.toolEntry = toolEntry;
		// next is required to see something :-)
		//	   setPreferredSize(new Dimension(DEFAULT_HANDLE_SIZE, DEFAULT_HANDLE_SIZE));
		setPreferredSize(new Dimension(getSmallImage()));
	}

	/**
	 * Paint the handle.
	 * 
	 * @param g 
	 */
	@Override
	public void paintFigure(Graphics g) {
		// Relocate the handle, so that it is paint at its correct place.
		getLocator().relocate(this);
		// Paint it
		Rectangle r = getBounds();
		g.setForegroundColor(getBorderColor());
		g.drawRectangle(r.x, r.y, r.width - 1, r.height - 1);
		g.setBackgroundColor(getFillColor());
		g.fillRectangle(r.x + 1, r.y + 1, r.width - 2, r.height - 2);

		Image img = getSmallImage();
		g.drawImage(img, r.x, r.y);
	}

	/**
	 * 
	 * 
	 * @return 
	 */
	@Override
	public IFigure getToolTip() {
		return createToolTip();
	}

	/**
	 * Create the tooltip figure.
	 * 
	 * @return 
	 */
	protected IFigure createToolTip() {
		String message = getToolTipText();
		if ((message == null) || (message.length() == 0)) {
			return null;
		}

		FlowPage fp = new FlowPage() {
			@Override
			public Dimension getPreferredSize(int w, int h) {
				Dimension d = super.getPreferredSize(-1, -1);
				if (d.width > 150) {
					d = super.getPreferredSize(150, -1);
				}
				return d;
			}
		};
		fp.setOpaque(true);
		fp.setBorder(TOOLTIP_BORDER);
		TextFlow tf = new TextFlow();
		tf.setText(message);
		fp.add(tf);
		return fp;
	}

	/**
	 * Get the text shown in the tooltip.
	 * 
	 * @return 
	 */
	protected String getToolTipText() {
		String text = null;
		PaletteEntry entry = toolEntry;
		String desc = entry.getDescription();
		boolean needName = nameNeededInToolTip();
		if ((desc == null) || desc.trim().equals(entry.getLabel())
				|| desc.trim().equals("")) { //$NON-NLS-1$
			if (needName) {
				text = entry.getLabel();
			}
		} else {
			if (needName) {
				text = entry.getLabel()
						+ " " //$NON-NLS-1$
						+ PaletteMessages.NAME_DESCRIPTION_SEPARATOR
						+ " " + desc; //$NON-NLS-1$
			} else {
				text = desc;
			}
		}
		if ((text != null) && text.trim().equals("")) {
			return null;
		}
		return text;
	}

	/**
	 * Do we want name in tooltip ?
	 * Default return false.
	 * 
	 * @return 
	 */
	protected boolean nameNeededInToolTip() {
		return false;
	}

	/**
	 * 
	 * 
	 * @return 
	 */
	protected Color getBorderColor() {
		return ColorConstants.black;
	}

	/**
	 * 
	 * 
	 * @return 
	 */
	protected Color getLineColor() {
		return ColorConstants.blue;
	}

	/**
	 * 
	 * 
	 * @return 
	 */
	protected Color getFillColor() {
		return lightBlue;
	}

	/**
	 * Get the small image.
	 * 
	 * @return 
	 */
	private Image getSmallImage() {
		if (smallImage == null) { // Create the image
			smallImage = createImage(toolEntry.getSmallIcon());
		}

		return smallImage;
	}

	/**
	 * Create an image from its name.
	 * 
	 * @param descriptor 
	 * 
	 * @return 
	 */
	protected Image createImage(ImageDescriptor descriptor) {
		return getImageCache().getImage(descriptor);
	}

	/**
	 * Creates a new drag tracker suitable for the toolEntry.
	 * 
	 * @return a new drag tracker
	 */
	@Override
	public DragTracker createDragTracker() {
		CreationFactory factory = (CreationFactory) toolEntry
				.getToolProperty(CreationTool.PROPERTY_CREATION_FACTORY);
		if (toolEntry instanceof ConnectionCreationToolEntry) {
			return new ConnectionDragCreationTool(factory);
		} else if (toolEntry instanceof CreationToolEntry) {
			return new CreationDragTool(factory, getOwner());
		} else {
			throw new UnsupportedOperationException(
					"No DragTracker defined for '" + toolEntry + "'.");
		}
	}

	/**
	 * Get the editpart to which this handle is associated.
	 * 
	 * @return 
	 */
	@Override
	public GraphicalEditPart getOwner() {
		return super.getOwner();
	}

	/**
	 * Returns the image cache.
	 * The cache is global, and is shared by all palette edit parts. This has the disadvantage
	 * that once an image is allocated, it is never freed until the display is disposed.
	 * However, it has the advantage that the same image in different palettes is
	 * only ever created once.
	 * 
	 * @return 
	 */
	protected static ImageCache getImageCache() {
		ImageCache cache = globalImageCache;
		if (cache == null) {
			globalImageCache = cache = new ImageCache();
			Display display = Display.getDefault();
			if (display != null) {
				display.disposeExec(new Runnable() {
					public void run() {
						if (globalImageCache != null) {
							globalImageCache.dispose();
							globalImageCache = null;
						}
					}
				});
			}
		}
		return cache;
	}

	/**
	 * Inner class.
	 * 
	 * @author dumoulin
	 */
	protected static class ImageCache {
		
		/**
		 * Map from ImageDescriptor to Image.
		 */
		private Map images = new HashMap(11);

		/**
		 * 
		 * 
		 * @param desc 
		 * 
		 * @return 
		 */
		Image getImage(ImageDescriptor desc) {
			if (desc == null) {
				return null;
			}
			Image img = null;
			Object obj = images.get(desc);
			if (obj != null) {
				img = (Image) obj;
			} else {
				img = desc.createImage();
				images.put(desc, img);
			}
			return img;
		}

		/**
		 * 
		 * 
		 * @return 
		 */
		Image getMissingImage() {
			return getImage(ImageDescriptor.getMissingImageDescriptor());
		}

		/**
		 * 
		 */
		void dispose() {
			for (Iterator i = images.values().iterator(); i.hasNext();) {
				Image img = (Image) i.next();
				img.dispose();
			}
			images.clear();
		}
	}

}
